#include "QtConnectionGraphicsObject.hpp"

#include <QtWidgets/QGraphicsSceneMouseEvent>
#include <QtWidgets/QGraphicsDropShadowEffect>
#include <QtWidgets/QGraphicsBlurEffect>
#include <QtWidgets/QStyleOptionGraphicsItem>
#include <QtWidgets/QGraphicsView>

#include "QtFlowScene.hpp"
#include "QtNode.hpp"
#include "QtConnection.hpp"
#include "QtNodeGraphicsObject.hpp"
#include "QtInteraction.hpp"

namespace Qt
{
	static
		QPainterPath
		cubicPath(ConnectionGeometry const& geom)
	{
		QPointF const& source = geom.source();
		QPointF const& sink = geom.sink();

		auto c1c2 = geom.pointsC1C2();

		// cubic spline
		QPainterPath cubic(source);

		cubic.cubicTo(c1c2.first, c1c2.second, sink);

		return cubic;
	}


	QPainterPath
		QtConnectionPainter::
		getPainterStroke(ConnectionGeometry const& geom)
	{
		auto cubic = cubicPath(geom);

		QPointF const& source = geom.source();
		QPainterPath result(source);

		unsigned segments = 20;

		for (auto i = 0ul; i < segments; ++i)
		{
			double ratio = double(i + 1) / segments;
			result.lineTo(cubic.pointAtPercent(ratio));
		}

		QPainterPathStroker stroker; stroker.setWidth(10.0);

		return stroker.createStroke(result);
	}


#ifdef NODE_DEBUG_DRAWING
	static
		void
		debugDrawing(QPainter * painter,
			QtConnection const & connection)
	{
		Q_UNUSED(painter);
		Q_UNUSED(connection);
		ConnectionGeometry const& geom =
			connection.connectionGeometry();

		{
			QPointF const& source = geom.source();
			QPointF const& sink = geom.sink();

			auto points = geom.pointsC1C2();

			painter->setPen(Qt::red);
			painter->setBrush(Qt::red);

			painter->drawLine(QLineF(source, points.first));
			painter->drawLine(QLineF(points.first, points.second));
			painter->drawLine(QLineF(points.second, sink));
			painter->drawEllipse(points.first, 3, 3);
			painter->drawEllipse(points.second, 3, 3);

			painter->setBrush(Qt::NoBrush);

			painter->drawPath(cubicPath(geom));
		}

		{
			painter->setPen(Qt::yellow);

			painter->drawRect(geom.boundingRect());
		}
	}
#endif

	static
		void
		drawSketchLine(QPainter * painter,
			QtConnection const & connection)
	{
		using Qt::ConnectionState;

		ConnectionState const& state =
			connection.connectionState();

		if (state.requiresPort())
		{
			auto const & connectionStyle =
				Qt::StyleCollection::connectionStyle();

			QPen p;
			p.setWidth(connectionStyle.constructionLineWidth());
			p.setColor(connectionStyle.constructionColor());
			p.setStyle(Qt::DashLine);

			painter->setPen(p);
			painter->setBrush(Qt::NoBrush);

			using Qt::ConnectionGeometry;
			ConnectionGeometry const& geom = connection.connectionGeometry();

			auto cubic = cubicPath(geom);
			// cubic spline
			painter->drawPath(cubic);
		}
	}

	static
		void
		drawHoveredOrSelected(QPainter * painter,
			QtConnection const & connection)
	{
		using Qt::ConnectionGeometry;

		ConnectionGeometry const& geom = connection.connectionGeometry();
		bool const hovered = geom.hovered();

		auto const& graphicsObject =
			connection.getConnectionGraphicsObject();

		bool const selected = graphicsObject.isSelected();

		// drawn as a fat background
		if (hovered || selected)
		{
			QPen p;

			auto const &connectionStyle =
				Qt::StyleCollection::connectionStyle();
			double const lineWidth = connectionStyle.lineWidth();

			p.setWidth(2 * lineWidth);
			p.setColor(selected ?
				connectionStyle.selectedHaloColor() :
				connectionStyle.hoveredColor());

			painter->setPen(p);
			painter->setBrush(Qt::NoBrush);

			// cubic spline
			auto cubic = cubicPath(geom);
			painter->drawPath(cubic);
		}
	}


	static
		void
		drawNormalLine(QPainter * painter,
			QtConnection const & connection)
	{
		using Qt::ConnectionState;

		ConnectionState const& state =
			connection.connectionState();

		if (state.requiresPort())
			return;

		// colors

		auto const &connectionStyle =
			Qt::StyleCollection::connectionStyle();

		QColor normalColorOut = connectionStyle.normalColor();
		QColor normalColorIn = connectionStyle.normalColor();
		QColor selectedColor = connectionStyle.selectedColor();

		bool gradientColor = false;

		if (connectionStyle.useDataDefinedColors())
		{
			using Qt::PortType;

			auto dataTypeOut = connection.dataType(PortType::Out);
			auto dataTypeIn = connection.dataType(PortType::In);

			gradientColor = (dataTypeOut.id != dataTypeIn.id);

			normalColorOut = connectionStyle.normalColor(dataTypeOut.id);
			normalColorIn = connectionStyle.normalColor(dataTypeIn.id);
			selectedColor = normalColorOut.darker(200);
		}

		// geometry

		ConnectionGeometry const& geom = connection.connectionGeometry();

		double const lineWidth = connectionStyle.lineWidth();

		// draw normal line
		QPen p;

		p.setWidth(lineWidth);

		auto const& graphicsObject = connection.getConnectionGraphicsObject();
		bool const selected = graphicsObject.isSelected();


		auto cubic = cubicPath(geom);
		if (gradientColor)
		{
			painter->setBrush(Qt::NoBrush);

			QColor c = normalColorOut;
			if (selected)
				c = c.darker(200);
			p.setColor(c);
			painter->setPen(p);

			unsigned int const segments = 60;

			for (unsigned int i = 0ul; i < segments; ++i)
			{
				double ratioPrev = double(i) / segments;
				double ratio = double(i + 1) / segments;

				if (i == segments / 2)
				{
					QColor c = normalColorIn;
					if (selected)
						c = c.darker(200);

					p.setColor(c);
					painter->setPen(p);
				}
				painter->drawLine(cubic.pointAtPercent(ratioPrev),
					cubic.pointAtPercent(ratio));
			}

			{
				QIcon icon(":convert.png");

				QPixmap pixmap = icon.pixmap(QSize(22, 22));
				painter->drawPixmap(cubic.pointAtPercent(0.50) - QPoint(pixmap.width() / 2,
					pixmap.height() / 2),
					pixmap);

			}
		}
		else
		{
			p.setColor(normalColorOut);

			if (selected)
			{
				p.setColor(selectedColor);
			}

			painter->setPen(p);
			painter->setBrush(Qt::NoBrush);

			painter->drawPath(cubic);
		}
	}


	void
		QtConnectionPainter::
		paint(QPainter* painter,
			QtConnection const &connection)
	{
		drawHoveredOrSelected(painter, connection);

		drawSketchLine(painter, connection);

		drawNormalLine(painter, connection);

#ifdef NODE_DEBUG_DRAWING
		debugDrawing(painter, connection);
#endif

		// draw end points
		ConnectionGeometry const& geom = connection.connectionGeometry();

		QPointF const & source = geom.source();
		QPointF const & sink = geom.sink();

		auto const & connectionStyle =
			Qt::StyleCollection::connectionStyle();

		double const pointDiameter = connectionStyle.pointDiameter();

		painter->setPen(connectionStyle.constructionColor());
		painter->setBrush(connectionStyle.constructionColor());
		double const pointRadius = pointDiameter / 4.0;
		painter->drawEllipse(source, pointRadius, pointRadius);
		painter->drawEllipse(sink, pointRadius, pointRadius);
	}

	QtConnectionGraphicsObject::
		QtConnectionGraphicsObject(QtFlowScene &scene,
			QtConnection &connection)
		: _scene(scene)
		, _connection(connection)
	{
		_scene.addItem(this);

		setFlag(QGraphicsItem::ItemIsMovable, true);
		setFlag(QGraphicsItem::ItemIsFocusable, true);
		setFlag(QGraphicsItem::ItemIsSelectable, true);

		setAcceptHoverEvents(true);

		// addGraphicsEffect();

		setZValue(-1.0);
	}


	QtConnectionGraphicsObject::
		~QtConnectionGraphicsObject()
	{
		_scene.removeItem(this);
	}


	Qt::QtConnection&
		QtConnectionGraphicsObject::
		connection()
	{
		return _connection;
	}


	QRectF
		QtConnectionGraphicsObject::
		boundingRect() const
	{
		return _connection.connectionGeometry().boundingRect();
	}


	QPainterPath
		QtConnectionGraphicsObject::
		shape() const
	{
#ifdef DEBUG_DRAWING

		//QPainterPath path;

		//path.addRect(boundingRect());
		//return path;

#else
		auto const &geom =
			_connection.connectionGeometry();

		return QtConnectionPainter::getPainterStroke(geom);

#endif
	}


	void
		QtConnectionGraphicsObject::
		setGeometryChanged()
	{
		prepareGeometryChange();
	}


	void
		QtConnectionGraphicsObject::
		move()
	{
		for (PortType portType : { PortType::In, PortType::Out })
		{
			if (auto node = _connection.getNode(portType))
			{
				auto const &nodeGraphics = node->nodeGraphicsObject();

				auto const &nodeGeom = node->nodeGeometry();

				QPointF scenePos =
					nodeGeom.portScenePosition(_connection.getPortIndex(portType),
						portType,
						nodeGraphics.sceneTransform());

				QTransform sceneTransform = this->sceneTransform();

				QPointF connectionPos = sceneTransform.inverted().map(scenePos);

				_connection.connectionGeometry().setEndPoint(portType,
					connectionPos);

				_connection.getConnectionGraphicsObject().setGeometryChanged();
				_connection.getConnectionGraphicsObject().update();
			}
		}
	}

	void QtConnectionGraphicsObject::lock(bool locked)
	{
		setFlag(QGraphicsItem::ItemIsMovable, !locked);
		setFlag(QGraphicsItem::ItemIsFocusable, !locked);
		setFlag(QGraphicsItem::ItemIsSelectable, !locked);
	}


	void
		QtConnectionGraphicsObject::
		paint(QPainter* painter,
			QStyleOptionGraphicsItem const* option,
			QWidget*)
	{
		painter->setClipRect(option->exposedRect);

		QtConnectionPainter::paint(painter,
			_connection);
	}


	void
		QtConnectionGraphicsObject::
		mousePressEvent(QGraphicsSceneMouseEvent* event)
	{
		QGraphicsItem::mousePressEvent(event);
		//event->ignore();
	}


	void
		QtConnectionGraphicsObject::
		mouseMoveEvent(QGraphicsSceneMouseEvent* event)
	{
		prepareGeometryChange();

		auto view = static_cast<QGraphicsView*>(event->widget());
		auto node = locateNodeAt(event->scenePos(),
			_scene,
			view->transform());

		auto &state = _connection.connectionState();

		state.interactWithNode(node);
		if (node)
		{
			node->reactToPossibleConnection(state.requiredPort(),
				_connection.dataType(oppositePort(state.requiredPort())),
				event->scenePos());
		}

		//-------------------

		QPointF offset = event->pos() - event->lastPos();

		auto requiredPort = _connection.requiredPort();

		if (requiredPort != PortType::None)
		{
			_connection.connectionGeometry().moveEndPoint(requiredPort, offset);
		}

		//-------------------

		update();

		event->accept();
	}


	void
		QtConnectionGraphicsObject::
		mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
	{
		ungrabMouse();
		event->accept();

		auto node = locateNodeAt(event->scenePos(), _scene,
			_scene.views()[0]->transform());

		QtInteraction interaction(*node, _connection, _scene);

		if (node && interaction.tryConnect())
		{
			node->resetReactionToConnection();
		}
		else if (event->button() == Qt::LeftButton)
		{
			auto nodeOut = _connection.getNode(PortType::Out);
			auto portOut = _connection.getPortIndex(PortType::Out);

			auto nodeIn = _connection.getNode(PortType::In);
			auto portIn = _connection.getPortIndex(PortType::In);

			if (nodeOut != nullptr && nodeIn == nullptr && portOut != INVALID_PORT)
			{
				_scene.outPortConextMenu(*nodeOut, portOut, event->screenPos());
			}
		}

		if (_connection.connectionState().requiresPort())
		{
			_scene.deleteConnection(_connection);
		}
	}


	void
		QtConnectionGraphicsObject::
		hoverEnterEvent(QGraphicsSceneHoverEvent* event)
	{
		_connection.connectionGeometry().setHovered(true);

		update();
		_scene.connectionHovered(connection(), event->screenPos());
		event->accept();
	}


	void
		QtConnectionGraphicsObject::
		hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
	{
		_connection.connectionGeometry().setHovered(false);

		update();
		_scene.connectionHoverLeft(connection());
		event->accept();
	}


	void
		QtConnectionGraphicsObject::
		addGraphicsEffect()
	{
		auto effect = new QGraphicsBlurEffect;

		effect->setBlurRadius(5);
		setGraphicsEffect(effect);

		//auto effect = new QGraphicsDropShadowEffect;
		//auto effect = new ConnectionBlurEffect(this);
		//effect->setOffset(4, 4);
		//effect->setColor(QColor(Qt::gray).darker(800));
	}
}